﻿using System;
using System.Xml.Linq;

namespace NDBGen
{
    [Flags]
    public enum DataType : long
    {
        All                 = 0xFFFFFFFFFFFFF,
        None                = 0x1,

        Bit                 = 0x2,
        TinyInt             = 0x4,
        SmallInt            = 0x8,
        Int                 = 0x10,
        BigInt              = 0x20,
        Real                = 0x40,
        Numeric             = 0x80,

        Decimal             = 0x100,
        Float               = 0x200,

        Money               = 0x400,
        SmallMoney          = 0x800,

        Date                = 0x1000,
        DateTime            = 0x2000,
        DateTime2           = 0x4000,
        SmallDateTime       = 0x8000,
        Time                = 0x10000,
        DateTimeOffset      = 0x20000,

        UniqueIdentifier    = 0x40000,

        Binary              = 0x80000,
        Timestamp           = 0x100000,
        Version             = 0x100000,
        VarBinary           = 0x200000,
        VarBinaryMax        = 0x400000,
        Image               = 0x800000,

        Char                = 0x1000000,
        NChar               = 0x20000000,
        NVarChar            = 0x40000000,
        NVarCharMax         = 0x80000000,
        VarChar             = 0x100000000,
        VarCharMax          = 0x200000000,
        Text                = 0x400000000,
        NText               = 0x800000000,

        Xml                 = 0x1000000000,

        SysName             = 0x2000000000,
        Variant             = 0x4000000000,
        UserDefinedType     = 0x8000000000,
        UserDefinedTableType = 0x10000000000,
        UserDefinedDataType  = 0x20000000000,
        HierarchyId         = 0x40000000000,
        Geometry            = 0x80000000000,
        Geography           = 0x100000000000
    }

    public static class DataTypeMapper
    {
        public static DataType Parse(String dataType)
        {
            var parts = dataType.Split(',');            

            String cleanedType = null;
            if (parts.Length == 1 || parts[1].ToUpperInvariant() != "MAX") cleanedType = parts[0];
            else cleanedType = dataType.Replace(",", String.Empty);

            try
            {
                return (DataType)Enum.Parse(typeof(DataType), cleanedType, true);
            }
            catch (ArgumentException ex)
            {
                throw new ArgumentException(dataType + " is not supported", "dataType", ex);
            }
        }

        public static Type Map(DataType type)
        {
            switch (type)
            {
                case DataType.Bit: return typeof(Boolean);

                case DataType.TinyInt: return typeof(Byte);

                case DataType.SmallInt: return typeof(Int16);

                case DataType.Int: return typeof(Int32);

                case DataType.BigInt: return typeof(Int64);

                case DataType.Decimal: return typeof(Decimal);

                case DataType.Float: return typeof(Single);

                case DataType.Date:
                case DataType.DateTime:
                case DataType.DateTime2:
                case DataType.SmallDateTime: return typeof(DateTime);

                case DataType.UniqueIdentifier: return typeof(Guid);

                case DataType.Xml: return typeof(XElement);

                case DataType.Binary:
                case DataType.Timestamp:
                case DataType.VarBinary:
                case DataType.VarBinaryMax: return typeof(Byte[]);

                case DataType.Money:
                case DataType.SmallMoney: return typeof(Decimal);

                case DataType.Char:
                case DataType.NVarChar:
                case DataType.NVarCharMax:
                case DataType.NChar:
                case DataType.Text:
                case DataType.NText:
                case DataType.VarChar:
                case DataType.VarCharMax: return typeof(String);
            }
            throw new NotSupportedException("The type '" + type.ToString() + "' is not supported.");
        }
    }

    public static class DataTypeExtensions
    {
        public static Boolean IsStringType(this DataType dataType)
        {
            return DataTypeMapper.Map(dataType) == typeof(String);
        }

        public static Boolean IsArrayType(this DataType dataType)
        {
            var type = DataTypeMapper.Map(dataType);
            return type.IsArray && type != typeof(String);

        }

        public static Boolean IsMaxLengthType(this DataType dataType)
        {
            switch (dataType)
            {
                case DataType.NVarCharMax:
                case DataType.VarBinaryMax:
                case DataType.VarCharMax:
                    return true;
            }

            return false;
        }

        public static Boolean IsBigType(this DataType dataType)
        {
            switch (dataType)
            {
                case DataType.Xml:
                case DataType.NText:
                case DataType.Text:
                case DataType.Binary:
                case DataType.VarBinaryMax:
                case DataType.VarCharMax:
                case DataType.NVarCharMax: return true;
            }

            return false;
        }

        public static Boolean RequiresQuotes(this DataType dataType)
        {
            switch (dataType)
            {
                case DataType.BigInt:
                case DataType.Binary:
                case DataType.Bit:
                case DataType.Decimal:
                case DataType.Float:
                case DataType.Int:
                case DataType.Money:
                case DataType.None:
                case DataType.Numeric:
                case DataType.Real:
                case DataType.SmallInt:
                case DataType.SmallMoney:
                case DataType.SysName:
                case DataType.Timestamp:
                case DataType.TinyInt:
                case DataType.VarBinary:
                case DataType.VarBinaryMax:
                    return false;
            }

            return true;
        }

        public static Boolean RequiresConvert(this DataType dataType)
        {
            switch (dataType)
            {
                case DataType.UniqueIdentifier:
                    return true;
            }

            return false;
        }
    }
}